#!/user/bin/env python
# -*- coding: utf-8 -*-
''' 
Author: Sjy
LastEditTime: 2024-09-06 10:01:50
Description: extree 提供自定义树控件,展示数据以及相关操作
事件执行逻辑:
    1. 执行事件时先递归构建事件链表
    2. 注意：TODO: 如果单元格是自定义的widget 如何处理比较好？
    事件链规则如下:
        1. 域(scope) 一旦给某个节点(一般是文件夹节点)设置了域属性后则会截断全部事件的冒泡行为,即传递到当前节点并执行完为止不会继续向上冒泡
        2. 自定义CELL的事件优先级最高,且不传播,即只会执行当前节点的cell事件不会继续向上传播
        3. 设置事件传播(propagation)规则,可设置参数如下,注意这些某些事件可以组合设置
            "no":不传递,仅执行cell绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,且不会再向上传递
                2. 如果是传播途径的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,且不会再向上传递
            "default": 缺省值,传递. 允许向上传递
                1.  当前触发事件的节点或者传播途径的节点,cell和node绑定的事件都会执行(先执行cell在执行node),且继续向上传递
            "cell": 传递,仅执行cell绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,会继续向上传递
            "node": 传递,仅执行node绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行node绑定的对应事件,cell绑定的对应事件会被忽略,会继续向上传递
                2. 如果是传播途径的节点,那么只会执行node绑定的对应事件,cell绑定的对应事件会被忽略,会继续向上传递
            "current": 传递，
                1. 如果是当前触发事件的节点,那么会执行cell和node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么会忽略掉当前节点和cell绑定的事件,不执行但会继续向上传递
            "non-current": 传递，仅非当前节点执行
                1. 如果是当前触发事件的节点,那么不会执行cell和node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么会执行节点和cell绑定的事件,不执行但会继续向上传递
        # 3. 设置的事件函数本身也可以控制事件的传递规则,在参数params中会提供event 属性执行 event.stop() 可以终止当前事件类型的传递
        #     比如鼠标点击时会触发 itemPressed 和 itemClicked, 如果在 itemPressed 事件执行时设置了 event.stop() 可以终止后续此类型的事件传播,但是 itemClicked 事件仍然会执行 可通过 event.stopAll() 终止后续所有事件的传播
        4. 事件允许传播过程如下(cell不是自定义的)
            cell事件 -> node 事件 -> 父节点cell事件 -> 父节点node事件 -> ... -> 根节点cell事件 -> 根节点node事件 -> 列头事件 -> 树事件
                注意如果传递过程中某个节点设置了域属性则会执行完域节点的事件后截断传递不在继续向上传递。
        

    事件使用注意事项:
        1. 如果想要使用某种类型事件则必须开启对应的默认事件,否则不会触发
        2. 尽量不要使用自己connect事件,因为不受控制
        3. 鼠标类事件还分 lbtn,rbtn,mbtn 三种,分别对应鼠标左键,右键,中键点击事件,例如点击事件可以分为 lbtnClick, rbtnClick, mbtnClick, click 四种其中click是鼠标任意键点击都会触发
            qt事件执行顺序 itemPressed -> itemClicked -> itemDoubleClicked -> itemActivated
                注意： rmenu > rbtnClicked  一般情况下启用了右键菜单就不要在启用右键事件了
                注意 qt默认只有鼠标左键点击才会触发 itemClicked 事件 所以如果想要实现右键单击 使用 mousePressEvent 重写
样式设置:
    样式传递的逻辑是与事件传递逻辑相反，如果事件传递是冒泡那么样式则是穿透，事件传递是从子节点递归到根节点，样式传递则是从根节点开始到最终的子节点。
    样式传递的规则如下:
        1. 存在可继承的样式和不可继承的样式
            可继承的样式如下：
                1. 背景色 bgcolor
                2. 前景色 color
                3. 字体 font 的大小、粗细、字体等
                4. 图标 icon
            不可继承的样式如下：
                1. 宽度 width
                2. 列中文本对齐方式 align
                3. 高度（高度要尽量统一。不统一会显示不好，也就是说不允许自定义高度，如果自定义了widget 那么要考虑设置整体的高度问题）
        2. 节点设置了 scope 那么样式传递同样会截断，scope 中的子节点的样式由 scope 所在节点以及后续的节点样式决定
        3. 样式传递默认是不传递， 如果需要则 
    
        样式主要包含如下几个方面
            1. 图标
            2. 字体
            3. 颜色：背景色、前景色
            4. 新增节点单元格的自定义样式
        注意有些设置不能被传递，比如列宽度
        

自定义cell：
    提供自定义组件比如显示颜色等。自定义组件的继承逻辑与样式类似,

'''

'''功能点如下
核心-考虑允许二次开发定制
按指定的数据结构即可显示数据,不需要关心显示的细节,只需要关心数据结构即可
后续考虑 checkbox 处理,暂时不考虑
'''

'''变量数据结构如下
__headers = [  注意在列头设置的样式和事件是所有cell的列默认样式和事件
    {# 第一列
        title: 显示的标题, 标题必须存在,title如果定义为True 则按 excel列顺序显示(待议)
        field: 数据字段名,没有则按列索引为字段名
        # checkable: 是否开启复选框,注意 这是与colType搭配的可搭配的项有 icon,iconText,text
        # radioable: 是否开启单选框,注意 这是与colType搭配的可搭配的项有 icon,iconText,text

        colType: 列默认单元格类型(可被子节点覆盖掉),
            label(默认),icon 图标类型,iconLabel 图标+文字类型, text可编辑文本,checkbox 复选框,radio 单选框,comboBox 下拉框, custom 自定义(自定义是传入自定义的widget,仍然可被具体节点覆盖),
        styles: {
            width: 列宽度 100 // 宽度只能tree设置或者猎头设置,不能被传递
            align: 对齐方式 left/center/right


            bgcolor: 背景色
            color: 字体颜色
            font: 字体
        } or function  列默认样式, 也可以是函数,根据数据动态返回样式
        events:{ #列默认事件列表, 也可以是函数,函数则会传入,
            # 交互事件
            lbtnClick: 鼠标左键点击
            rbtnClick: 鼠标右键点击
            mbtnClick: 鼠标中键点击
            click: 点击事件,只要点击就会触发(鼠标任意键点击都会触发)
            dbClick: 双击事件

            ...
            # 生命周期事件
            onInit 初始化事件,仅节点初次创建时调用
            onDestroy销毁事件,节点销毁时调用
        },
        propagations:{ #传播 定义具体事件的传播规则
            lbtnClick:""  # 传递规则,默认为default,即只传递到当前节点并执行不会继续向上冒泡
            rbtnClick:"current"
        }，
        inherit：{

        }
    }
]

__gids = {
    uuid4:{
        widgetItem,
        注意外部存放显示对应信息 比如第一列的field值是name 那么如果数据中有对应的值则可以直接显示例如
        name: "xxx", # 对应列头的 field 的值 这样就会显示在第一列

        data:{}, # data 此key一般用于作为与后台数据库交互的内容,不显示在树上
        scope:"", # 域,设置域后则会截断事件传播,即只会传递到当前节点并执行完为止不会继续向上冒泡,可以设置为True 或者具体的域名
        styles:{
            [headerName0]: { 根据 name 确定使用哪个样式,如果未定义则向上查找继承列头的样式或者tree设置的样式
            }  
        },
        events:{
            [headerName0]: {
                lbtnClick:lambda params:print(f"xxx :  {headerName0} 左键点击事件 参数 {params}"),
            }   
            lbtnClick:lambda params:print(f"xxx 左键点击事件,参数 {params}"),

        }
    }
}
'''
__all__= []

# 导入相关模块
import uuid
import copy
# 引入QT相关模块
from PyQt5 import QtWidgets, QtCore, QtGui

# 引入自定义模块
try:
    from .utils import print_error, is_empty, is_dict, is_fun, is_list
except:
    """在 根目录下单独测试"""
    import utils as utils

global FLAGS 
FLAGS = {
    "gid": QtCore.Qt.UserRole,              # 绑定数据标志
    "nodeType": QtCore.Qt.UserRole + 1,     # 节点类型标志
    "pressAccept": QtCore.Qt.UserRole + 2,  # 鼠标按压事件标志
}

global DEFAULT_ACTION_KEYS # 操作tree提供默认事件列表可以设置启用还是不启用
DEFAULT_ACTION_KEYS = ["itemClicked", "itemDoubleClicked", "itemActivated", "itemPressed", "currentItemChanged", "itemSelectionChanged", "customContextMenuRequested"]  #  "itemSelectionChanged", "currentItemChanged" 等事件

class TreeNode(QtWidgets.QTreeWidgetItem):
    def __init__(self, gid, *args, **kwargs):
        super(TreeNode, self).__init__(*args, **kwargs)
        self.setData(0, FLAGS.get("gid"), gid)
        self.setFlags( self.flags() |  QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
    def _gid(self):
        try:
            return self.data(0, QtCore.Qt.UserRole)
        except:
            print("未知原因获取ID失败")
            print_error()
        return None
    def _is_leaf(self):
        nodeType = self.data(0, FLAGS.get("nodeType"))
        return nodeType and nodeType == "leaf"
    def _is_branch(self):
        nodeType = self.data(0, FLAGS.get("nodeType"))
        return nodeType and nodeType == "branch"
class LeafNode(TreeNode):
    def __init__(self, gid, *args, **kwargs):
        super(LeafNode, self).__init__(gid, *args, **kwargs)
        self.setData(0, FLAGS.get("nodeType"), "leaf")
class BranchNode(TreeNode):
    def __init__(self, gid, *args, **kwargs):
        super(BranchNode, self).__init__(gid, *args, **kwargs)
        self.setData(0, FLAGS.get("nodeType"), "branch")
class _Tree(QtWidgets.QTreeWidget):
    def __init__(self, parent: QtWidgets.QWidget = None):
        super(_Tree, self).__init__(parent)
        self.setUniformRowHeights(True) # 所有节点的高度保持相同
        self.setRootIsDecorated(True)   # 设置根节点 是否显示展开折叠图标  True 显示,False 不显示（仅限根节点）
        self.setHeaderHidden(False)     # 默认不隐藏头 显示头部标题
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)  # 打开右键菜单的策略
        self.setExpandsOnDoubleClick(False) # 设置是否双击展开节点 当前不展开
    def mousePressEvent(self, event):
        # 鼠标点击事件,判断当前点击位置是否有item且满足标志否则拦截鼠标事件
        """例如设置父节点不可选中且不能执行相应的点击事件 注意配合下面的 mousePressEvent 方法使用
            pitem1 = QTreeWidgetItem(self, ["parent item 1"])
            # 设置不可选中
            pitem1.setFlags(pitem1.flags() & ~Qt.ItemIsSelectable)
            # 设置一个标识用于屏蔽鼠标事件,这样就不会触发点击事件
            pitem1.setData(0, Qt.UserRole + 1, True)
        """
        item = self.itemAt(event.pos())
        if item and item.data(0, FLAGS.get("pressAccept")):
            event.accept()
            return
        if event.button() == QtCore.Qt.RightButton or event.button() == QtCore.Qt.MiddleButton:
            item = self.itemAt(event.pos())
            if item is not None:
                # 获取当前点击的col 
                col = self.columnAt(event.pos().x())
                self.itemClicked.emit(item, col)  # 发出自定义的点击信号
        super(_Tree, self).mousePressEvent(event)
    def closeEvent(self, event):
        # 断开与 QTreeWidget 相关的所有信号-槽连接
        QtCore.QObject.disconnect(self)
        event.accept()
class BaseTree(_Tree):
    """基础树,提供全部的自定义基础功能"""
    def __init__(self, params:dict={}, parent = None):
        super(BaseTree, self).__init__(parent)
        # params = {
        #     "propagation":True, # 开启事件传递，注意默认是开启的
        #     "inherit":True, # 开启样式继承 默认是开启的
        # }
        self._propagation = params.get("propagation", True) # 是否开启事件传递，注意当前节点的传播规则设置依然有效，只是与scope 类似不会继续向父节点传播
        self._inherit = params.get("inherit", True)  # 是否开启样式继承

        self.__flagDbclickEvent = False     # 双击标志,用于判断是否触发双击事件屏蔽单击事件
        self.__init_vars()              # 初始化变量
        self._init_header_sets()               # 自动初始化相关配置信息
    def __init_vars(self):
        """初始化变量"""
        self.__headers = {}         # 列头, 缓存列头信息,用于根据列头定义初始化数据行各个单元格类型以及赋值等信息
        self.__gids = {}            # 记录节点id 数据、事件、样式映射关系表,这样每个树中仅绑定数据的gid,不绑定数据,数据通过gid获取,好处是如果指导gid可以直接获取信息而不需要通过tree访问
        self.__headerTitles = []    # 列头标题
        self.__key_to_widget = {}        # 记录查询的key 一般用于获取widgetItem]
    def _init_header_sets(self):
        """初始化列头配置"""
        self.header().setStretchLastSection(False) # 设置是否铺满宽度 默认不铺满
    def _set_dbclick_event_flag(self, flag:bool):
        """设置双击标志"""
        self.__flagDbclickEvent = flag
    def _get_dbclick_event_flag(self):
        """获取双击标志"""
        return self.__flagDbclickEvent
    # gid数据信息处理  =====================================
    def _gid_item(self, gid:str=None, type:str="data"):
        """获取gid的数据, 默认获取缓存的数据"""
        if not gid:return {}
        try:
            if type != None:
                return self.__gids.get(gid, {}).get(type, {})
            return self.__gids.get(gid, {})
        except:
            return {}
    def _set_gid_item(self, gid:str, item:dict):
        """设置gid的数据"""
        if not gid:return
        self.__gids[gid] = item
    def _set_gid_key_value(self, gid:str, key:str, value:any):
        """设置gid的数据"""
        if not gid:return
        gidItem = self.__gids.get(gid, {})
        gidItem[key] = value
        self._set_gid_item(gid, gidItem)
    def _gid_styles(self, gid):
        """获取gid映射的样式"""
        return self._gid_item(gid, "styles")
    def _gid_events(self, gid):
        """获取gid映射的事件"""
        return self._gid_item(gid, "events")
    def _gid_propagations(self, gid):
        """获取gid映射的事件传播规则"""
        return self._gid_item(gid, "propagations")
    def _gid_scope(self, gid):
        """获取gid映射的域"""
        return self._gid_item(gid, "scope")
    def _gid_data(self, gid:uuid.uuid4, deepcopy:bool=True):
        """获取gid映射的实际数据
            参数释义:
                1. gid: 节点的gid
                2. deepcopy: 是否深拷贝数据,默认深拷贝
        """
        data = self._gid_item(gid, "data") or {}
        return deepcopy and copy.deepcopy(data) or data
    def _set_gid_data(self, gid, data:dict={}):
        """设置gid映射的数据"""
        gidItem = self._gid_item(gid)
        gidItem["data"] = data
    def _get_widgetitem_data(self, widgetItem:QtWidgets.QWidgetItem):
        gid = widgetItem._gid()
        if gid:
            return self._gid_data(gid)
        return {}
    def _set_widgetitem_data(self, widgetItem:QtWidgets.QTreeWidgetItem, data:dict={}):
        """设置绑定数据"""
        gid = widgetItem._gid()
        self._set_gid_key_value(gid, "data", data)
    def _gid_widget_item(self, gid):
        """获取gid映射的widgetItem"""
        return self._gid_item(gid, "__WIDGET_ITEM")
    def _gid_map(self, gid, widgetItem, data:dict={}):
        """映射gid数据,新增需要保存的某种数据信息时要记得添加key"""
        if not gid or not widgetItem:return 
        if not is_dict(data):
            # print("userdata必须是字典类型")
            data = {}
        self.__gids[gid] = {
            **data,
            "__WIDGET_ITEM":widgetItem, # 保存的映射数据自定义的部分使用双下划线+大写字母表示,防止与数据中的key存在冲突因此数据中不能定义这种格式的key
        }
        """
            下面注释掉的key 如 data,styles,events,scope,propagations 是可被系统识别的key,如果定义了这些key则会被系统识别为特殊数据，具备特殊用途，因此对这些key的使用要慎重
            "data":data.get("data", {}) data 绑定交互的数据,这个数据不会显示在树上,但是可以通过gid获取,这里一般存储真实数据
            "styles":data.get("styles", {}) , styles 样式表，用于设置当前节点的样式
            "events": data.get("events", {}), events 事件表,用于设置当前节点或者cell的事件
            "scope": data.get("scope", None), scope 域,设置域后则会截断事件传播,即只会传递到当前节点并执行完为止不会继续向上冒泡
            "propagations": data.get("propagations", {}) propagations 事件传播规则,用于设置具体事件的传播规则
        """
    def _get_widget_item(self, key:str, value:any, record:bool=True):
        """根据指定的key以及对应的value查找widgetItem
            1. 每个节点都有一个唯一的gid，且都通过gid进行了映射处理，因此遍历gids 集合即可找到对应的widgetItem
            参数释义:
                1. key: 查找的key
                2. value: 找到key匹配的value相同的widgetItem
                3. record: 是否记录查找到的widgetItem,默认不记录，一旦记录则视为缓存，下次查找时直接从缓存中获取
                    TODO:需要考虑节点删除后的更新缓存问题
        """
        if record and value in self.__key_to_widget:
            # 要判断下已存在的widgetItem 是否还在tree中
            widgetItem = self.__key_to_widget[value]
            if widgetItem.treeWidget() is self:
                return widgetItem
            self.__key_to_widget.pop(value)
        # 遍历gids集合 获取指定key对应的widgetItem
        for gid, item in self.__gids.items():
            if item.get(key) == value:
                widgetItem = item.get("__WIDGET_ITEM")
                if record:
                    self.__key_to_widget[value] = item.get("__WIDGET_ITEM")
                return item.get("__WIDGET_ITEM")
    def _get_gids(self):
        return self.__gids
    # 标题头处理  =====================================
    def _set_headers(self, headers:list=[], render:bool=True):
        """设置数据"""
        # 先检查数据是否合法 必须有 title 和 field 这两个属性
        try:
            for header in headers:
                if not "title" in header or not "field" in header:
                    raise Exception("列头必须包含 title 和 field 两个属性")
        except Exception as e:
            print("An error occurred:", e)
            return 
        self.__headers = headers
        if render:
            self._render_headers()
    def _get_headers(self):
        """获取列头"""
        return self.__headers
    def _render_headers(self):
        """渲染头部"""
        headers = self.__headers
        # 根据列头自动确定显示的列数 暂不考虑隐藏列问题
        self.setColumnCount(len(headers))
        headerTitles = []
        for i in range(len(headers)):
            header = headers[i]
            headerTitles.append(header.get("title", ""))
            if "width" in header:
                """有宽度则固定,否则自适应"""
                self.header().resizeSection(i, header.get("width", "") )
                self.header().setSectionResizeMode(i, QtWidgets.QHeaderView.Fixed)
            else:
                self.header().setSectionResizeMode(i, QtWidgets.QHeaderView.Stretch)
        # 设置列头名称到tree 上
        self._set_header_titles(headerTitles)
    def _set_header_titles(self, titles:list=[], readerTitle:bool=True):
        """设置列头标题"""
        self.__headerTitles = titles
        readerTitle and self.setHeaderLabels(titles)
    def _get_headers_titles(self):
        """获取列头标题"""
        return self.__headerTitles
    def _title_to_col(self, title):
        """根据列头名确定当前列头所在的列"""
        return self.__headerTitles.index(title)
    def _field_to_col(self, field):
        """根据定义的字段名确定当前列头所在的列"""
        for index in range(len(self.__headers)):
            item = self.__headers[index]
            if item.get("field") == field:
                return index
        return -1
    def _col_to_field(self, col):
        """根据列确定字段名"""
        return self.__headers[col].get("field", col)
    def _col_to_event(self, col):
        """根据列确定事件"""
        return self.__headers[col].get("events", {})
    # 数据处理  =====================================
    def _is_propagation(self):
        """获取tree是否开启事件传播规则"""
        return self._propagation
    def _is_inherit(self):
        """获取tree是否开启样式继承规则"""
        return self._inherit
    def _set_data(self, data:list=[]):
        """给tree设置数据,注意这个函数直接重新生成tree数据,不会保留原有数据"""
        self._init_tree_items(data)     
    def _init_tree_items(self, data:list=[], pWidgetItem:TreeNode=None):
        """初始化tree显示数据
            data: 数据列表
            pWidgetItem: 父节点的widgetItem 如果未指定,则默认为直接从根部添加数据
            1. 先清理当前节点下的所有数据
            2. 添加数据
        """
        self._delete(pWidgetItem) # 默认没有传入根节点的时候清理
        self._init_children(pWidgetItem, data)
        not pWidgetItem and self._expand_roots() # 默认展开根节点
    def _expand_roots(self):
        """展开根节点"""
        for rootWidgetItem in self._get_root_widgetitems():
            rootWidgetItem.setExpanded(True)
    def _get_root_widgetitems(self):
        """获取根节点列表"""
        roots = []
        for i in range(0, self.topLevelItemCount()):
            """循环获取根节点"""
            rootWidgetItem = self.topLevelItem(i) # 获取顶层节点
            roots.append(rootWidgetItem)
        return roots
    def _is_root(self, widgetItem:QtWidgets.QTreeWidgetItem):
        """判断是否为根节点"""
        return True if widgetItem.parent() is None else False 
    def _init_children(self, pWidgetItem, data):
        """初始化子节点"""
        for item in data:
            children = None
            if "children" in item:
                children = item.get("children", [])
                # 处理完后清除掉 children 防止内存过大
                del item["children"]
            widgetItem = self._init_tree_item(pWidgetItem or self, item, children)
            if children:
                self._init_children(widgetItem, children)
    def _init_tree_item(self, parent, data, children):
        """初始化节点信息,注意parent可能是tree本身
            1. 如果含有 children 属性则认为是branch节点否则未leaf节点
            2. 初始化gid
        """
        gid = uuid.uuid4()
        widgetItem = children!=None and BranchNode(gid, parent) or LeafNode(gid, parent)
        self._gid_map(gid, widgetItem, data)
        headers = self._get_headers()
        for i in range(len(headers)):
            header = headers[i]
            field = header.get("field", i)
            widgetItem.setText(i, str(data.get(field, "")))
            icon = data.get("icon", "")
            if icon:
                widgetItem.setIcon(i, QtGui.QIcon(icon))
            self._set_item_style(widgetItem, field, data.get("styles", {}))
        return widgetItem
    def _set_item_style(self, widgetItem:QtWidgets.QTreeWidgetItem, field, styles:dict={}):
        """设置节点样式
        """
        col = self._field_to_col(field)
        # print("col", col)   
        
        # colStyles = styles.get(field, {})
        curColStyle = styles.get(field, {})
        # print("curColStyle", field, curColStyle)
        for key, value in curColStyle.items():
            if key == "bgcolor":
                widgetItem.setBackground(col, QtGui.QBrush(QtGui.QColor(value)))
            elif key == "color":
                widgetItem.setForeground(col, QtGui.QBrush(QtGui.QColor(value)))
            elif key == "font":
                widgetItem.setFont(col, QtGui.QFont(value))



    def _delete(self, widgetItem:QtWidgets.QTreeWidgetItem=None, includeItself:bool=False):
        """清理节点
            widgetItem: 需要清理的节点,如果没有传入则默认删除所有tree中的节点
            includeItself: 是否包含节点本身,默认不包含,只清理子节点,需要注意如果当前清理的节点是叶子节点则会默认清理自身第二参数无效。
        """
        if not widgetItem:
            # 清理记录的缓存信息
            self._clear_caches()
            # 清空tree
            return self.clear()
        # 判断当前节点是否是叶子节点
        if widgetItem._is_leaf() or includeItself:
            """清理包含自身的节点
                1. 判断当前是否为跟节点,如果当前为跟节点则移除当前跟节点
                    如果不是则移除当前节点
                2. 清理节点
            """
            isRootNode = self._is_root(widgetItem)
            self._clear_gids_cache(self._get_gids(widgetItem))
            if isRootNode:
                rootIndex = self.indexOfTopLevelItem(widgetItem)
                self.takeTopLevelItem(rootIndex)
            else:
                parent = widgetItem.parent()
                if parent:
                    parent.removeChild(widgetItem)
        else:
            """清理子节点"""
            self._clear_gids_cache(self._get_gids(widgetItem)[1:])
            widgetItem.takeChildren()
    def _get_gids(self, widgetItem:QtWidgets.QTreeWidgetItem=None):
        """获取widgetItem包含自身和子节点的全部gid集合,递归遍历所有深层次的节点"""
        gids = []
        gid = widgetItem._gid()
        if gid:
            gids.append(gid)
            count = widgetItem.childCount()
            for j in range(0, count):
                curWidgetItem = widgetItem.child(j)
                # 如果当前节点有子节点则递归编辑否则直接添加当前节点的gid
                curWidgetItem.childCount()  > 0 and gids.extend(self._get_gids(curWidgetItem)) and gids.append(curWidgetItem._gid())
        return gids
    def _clear_gids_cache(self, gids:list=[]):
        """清理gid缓存"""
        for gid in gids:
            self.__gids.pop(gid)
    def _clear_caches(self):
        """清理缓存的数据"""
        self.__gids = {}
        self.__key_to_widget = {}
    def _destroy(self):
        """销毁树"""
        # 清理缓存数据
        self._clear_caches()
        # 清楚内存中的数据
        self.clear()
        # 清理树本身包含的槽函数
        QtCore.QObject.disconnect(self)
class DataTree(BaseTree):
    """仅展示数据,不带操作"""
    def __init__(self, params:dict={}, parent = None):
        super(DataTree, self).__init__(params, parent=parent)
class OpTree(BaseTree):
    """带有操作的树"""
    sigPopEvent = QtCore.pyqtSignal(object) # 右键菜单信号
    def __init__(self, params:dict={}, parent = None):
        super(OpTree, self).__init__(params, parent=parent)
        self.__events = [] # 事件链集合
        """__events 允许设置的key如下
            lbtnClick: 鼠标左键点击
            rbtnClick: 鼠标右键点击
            mbtnClick: 鼠标中键点击
            click: 点击事件,只要点击就会触发(鼠标任意键点击都会触发)
            dbClick: 双击事件 注意只有鼠标左键有双击事件 因此默认只有一个双击事件即可
            ...
        """
        self.__enableSignals = {} # 启用的信号列表
        self.__signalActions = {} # 顶层树绑定事件集合 key 为事件名,值为事件函数
        self.__temp_rmenus = []   # 记录临时右键菜单
        self.__temp_params = {}   # 记录临时参数 用于事件传递时额外的自定义参数 在对应类型事件结束时销毁 注意其结构为 {event1: {key: value, ...}, event2: {key: value, ...}, ...}
        
    # 关联信号函数  =====================================
    def __enable_signal(self, key:str):
        """添加启用的信号"""
        self.__enableSignals[key] = True
    def _clear_enable_signals(self):
        """清除启用的默认事件集合"""
        self.__enableSignals = {}
    def _get_enable_signals(self):
        """获取启用的默认事件集合"""
        return self.__enableSignals
    def _start_enable_signals(self, useActionKeys:list=[], useAll:bool=False):
        """启动可用信号
            itemClicked: 点击事件
            itemDoubleClicked: 双击事件
            rclick: 右键点击事件
            itemActivated: 激活事件
            itemPressed: 鼠标按下事件
        useAll: 是否使用全部事件 默认为False, 如果值为True则使用全部事件 忽略第一个参数
        """
        if not useAll and not is_list(useActionKeys):
            return print("OpTree._start_enable_signals(useActionKeys:list=[], useAll:bool=False) 参数 useActionKeys 必须是列表!")
        if useAll:
            useActionKeys = DEFAULT_ACTION_KEYS
        # 清楚所有的槽函数
        for key in useActionKeys or []:
            if key in DEFAULT_ACTION_KEYS:
                """key 必须在 DEFAULT_ACTION_KEYS 中否则无效"""
                eventName = f"_on_{key}"
                action =  hasattr(self, f"_on_{key}") and getattr(self, f"_on_{key}")
                
                # 注意这里不能判断 is_fun 判断是否为函数 因为通过 getattr 获取的是 method 对象
                self._set_signal_action(key, action)
                self.__enable_signal(key)
    def _set_signal_action(self, key:str, action, oldAction=None):# 
        """单独设置某个信号的槽函数,也可以删除或者覆盖
            key: 事件名,值可能如下: 
                "itemClicked"     : 点击事件
                "itemDoubleClicked"   : 双击事件
                "itemActivated" : 激活事件
                "itemPressed"   : 鼠标按下事件
                ...
            action: 事件函数
            oldAction: 旧事件函数 用来替换或者删除的旧的事件函数
        """
        self.__deal_signal(key, action, oldAction)
    def __deal_signal(self, eventName, action, oldAction):
        """处理信号,链接、删除、替换槽函数"""
        # print(f"启用信号: {eventName}")
        event = getattr(self, eventName)
        try:
            if action:
                if oldAction:
                    event.disconnect(oldAction)
                event.connect(action)
                # print(f"\t启用{eventName}信号成功!")
            else:
                if oldAction: 
                    event.disconnect(oldAction)
                else:
                    event.disconnect()
        except:
            print_error()
    def _disconnect_singals(self):
        """清理关联的信号的槽函数"""
        enableActions = self._get_enable_signals()
        for key in enableActions.keys():
            action = getattr(self, f"_on_{key}")
            self._set_signal_action(key, None, oldAction=action)
    # 自定义信号事件处理  =================================
    def _add_action(self, key:str, action):
        """添加tree事件"""
        self.__signalActions[key] = action
    def _get_actions(self):
        """获取事件集合"""
        return self.__signalActions
    def _get_key_action(self, key:str):
        """获取事件"""
        return self.__signalActions.get(key)
    def _clear_actions(self):
        """清除事件集合"""
        self.__signalActions = {}
    def _add_pressed(self, action):
        """设置自定义tree的按压事件"""
        self._add_action("pressed", action)
    def _add_lbtn_click(self, action):
        """设置鼠标左键点击事件"""
        self._add_action("lbtnClick", action)
    def _add_rbtn_click(self, action):
        """设置鼠标右键点击事件"""
        self._add_action("rbtnClick", action)
    def _add_mbtn_click(self, action):
        """设置鼠标中键点击事件"""
        self._add_action("mbtnClick", action)
    def _add_click(self, action):
        """设置鼠标任意键点击事件"""
        self._add_action("click", action)
    def _add_dbclick(self, action):
        """设置鼠标双击事件"""
        self._add_action("dbClick", action)
    def _add_activated(self, action):
        """设置激活事件, 在pyqt5 中缺省状态下 鼠标左键双击后会触发激活事件"""
        self._add_action("activated", action)    
    def _add_rmenu(self, action):
        """添加右键菜单项,action 要返回右键菜单项列表"""
        self._add_action("rmenu", action)



    def _show_menu(self, menuItems:list=[]):
        """显示菜单
            menuItems: 菜单项列表
            例如：
                menuItems = [
                    {
                        "title":"菜单1",
                        "action":lambda:print("菜单1")
                    },
                    { 
                        "title":"菜单2",
                        "children":[
                            {
                                "title":"子菜单1",
                                "action":lambda:print("子菜单1")
                            }
                        ]
                    },
                    {}, # 分割线
                ]
        """
        menu = QtWidgets.QMenu()
        self.__init_menu(menu, menuItems)
        menu.exec_(QtGui.QCursor.pos())
    def __init_menu(self, menu:QtWidgets.QMenu, items:list=[]):
        for item in items:
            if 'title' in item:
                if 'children' in item: #存在子菜单
                    submenu = QtWidgets.QMenu(item.get("title"), self)
                    self.__init_menu(submenu, item.get("children", []))
                    menu.addMenu(submenu)
                elif 'action' in item and isinstance(item.get("action"), type(lambda x:x)):
                    action =  menu.addAction(item.get("title"))
                    action.triggered.connect(item.get("action"))
            else:
                menu.addSeparator()

    # 信号对应函数 =================================
    def _on_itemPressed(self, widgetItem, col):
        """默认鼠标按下事件"""
        # print("_on_itemPressed", widgetItem, col)
        self.__init_events("itemPressed")
        self.__build_events(widgetItem, col, ["itemPressed"])
        self.__run_events()
    def _on_itemClicked(self, widgetItem, col):
        """触发默认点击事件
            QtWidgets.QApplication.doubleClickInterval()+50 这段代码含义是 返回双击时间的间隔
                原理是要延迟执行当前的点击函数,根据qt系统双击的响应时间+50ms来判断是否触发双击事件,如果触发了双击事件则不执行单机事件否则执行单机事件
        """
        mouse_event = QtWidgets.QApplication.mouseButtons()
        QtCore.QTimer.singleShot(
            QtWidgets.QApplication.doubleClickInterval()+50, # 获取到鼠标双击的时间+50ms间隔用于判断是否触发双击事件
            lambda *args, widgetItem=widgetItem, col=col, mouse_event=mouse_event, **kwargs:self.__delay_click_action(widgetItem, col, mouse_event)
        )  
    def _on_itemDoubleClicked(self, widgetItem, col):
        """默认双击事件
            1. 仅处理左键双击事件
            2. 默认的双击事件会触发设置的自定义响应函数,如果自定义响应函数未实现则不会触发
        """
        self.__init_events("itemDbClicked")
        self._set_dbclick_event_flag(True)
        self.__build_events(widgetItem, col, ["dbClick"])
        self.__run_events()
    def _on_itemActivated(self, widgetItem, col):
        """默认激活事件"""
        self.__init_events("activated")
        self.__build_events(widgetItem, col, ["activated"])
        self.__run_events()
    def _on_currentItemChanged(self, currentTreeWidgetItem, previousTreeWidgetItem):
        """自定义当前节点改变事件 由信号 currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
        currentTreeWidgetItem: 当前节点
        previousTreeWidgetItem: 上一个节点
        """
        self.__init_events("currentItemChanged")
        self.__build_events(currentTreeWidgetItem, 0, ["activated"], params={"previousTreeWidgetItem":previousTreeWidgetItem})
        self.__run_events()
    def _on_itemSelectionChanged(self):
        """自定义当前节点选择发生变化执行的函数 由信号 itemSelectionChanged() 触发"""
        self.__init_events("itemSelectionChanged")
        selected_items = self.selectedItems()
        for widgetItem in selected_items:
            self.__init_events("itemSelectionChanged")
            self.__build_events(widgetItem, 0, ["itemSelectionChanged"])
            self.__run_events()
    def _on_customContextMenuRequested(self, point:QtCore.QPoint=None):
        """自定义右键菜单事件,传入的参数是当前点击的位置, 由信号 customContextMenuRequested(const QPoint &pos) 触发，事件类型确定为 rmenu 。 
            右键菜单指定的事件与其他事件是有区别的,在传入的事件中,使用者需要构建显示的菜单项 _get_temp_rmenus 获取已有的 _set_temp_rmenus 创建要显示的菜单 , 最终交由系统自行显示。
        """
        try:
            self.sigPopEvent.disconnect(self.__slot_pop_rmenu_event)
        except TypeError:
            pass

        self.sigPopEvent.connect(self.__slot_pop_rmenu_event)
        widgetItem = self.currentItem()
        col = self.currentColumn()
        if widgetItem:
            self.__init_events("rmenu")
            self.__build_events(widgetItem, col, ["rmenu"])
            self.__run_events()
    def __slot_pop_rmenu_event(self, params:dict={}):
        """当rmenu事件全部执行完成时触发的槽函数"""
        event = params.get("event")
        if event and event == "rmenu":
            """注意执行的事件可能不是rmenu事件,因此需要判断当前执行的事件是否是rmenu事件"""
            tempRmenus = self._get_temp_rmenus()
            if is_list(tempRmenus):
                self._show_menu(tempRmenus)
                self._clear_temp_rmenus()
    def _get_temp_rmenus(self):
        """获取临时右键菜单项"""
        return self.__temp_rmenus or []
    def _set_temp_rmenus(self, menus:list=[]):
        """设置临时右键菜单项"""
        self.__temp_rmenus = menus
    def _clear_temp_rmenus(self):
        """清理临时右键菜单项"""
        self.__temp_rmenus = []
    def _get_temp_params(self):
        """获取临时参数
            {event1: {key: value, ...}, event2: {key: value, ...}, ...}
        """
        return self.__temp_params or {}
    def _set_temp_params(self, params:dict={}):
        """设置临时参数
            params : {event1: {key: value, ...}, event2: {key: value, ...}, ...}
        """
        self.__temp_params = params
    def _clear_temp_params(self, curEvent:str=None):
        """清理临时参数"""
        if curEvent and curEvent in self.__temp_params:
            self.__temp_params.pop(curEvent)
        elif not curEvent:
            self.__temp_params = {}

    # 事件处理  =====================================
    # 点击触发函数
    def __delay_click_action(self, widgetItem, col, mouse_event, *args, **kwargs):
        """延迟执行点击事件"""
        if self._get_dbclick_event_flag():
            """判断是否双击,双击则恢复默认标志,不是双击则正常执行响应单机响应事件"""
            self._set_dbclick_event_flag(False)
            # self.__run_dbclick_action(widgetItem, col, mouse_event)
        else:
            self.__run_click_action(widgetItem, col, mouse_event)
    def __run_click_action(self, widgetItem, col, mouse_event):
        """运行单击事件触发行为"""
        self.__init_events("itemClicked")
        # 需要注意的是如果双击的时间内点击了其他节点则需要根据最后一个处理一下 否则会出现当前节点单击命令被执行两次情况
        # 优先执行具体的鼠标点击事件
        if mouse_event == QtCore.Qt.RightButton:
            self.__build_events(widgetItem, col, ["rbtnClick", "click"])
        elif mouse_event == QtCore.Qt.MiddleButton:
            self.__build_events(widgetItem, col, ["mbtnClick", "click"])
        else:
            self.__build_events(widgetItem, col, ["lbtnClick", "click"])
        self.__run_events()
    # def __run_dbclick_action(self, widgetItem, col, mouse_event):
    #     self.__init_events("itemDbClicked")
    #     self._set_dbclick_event_flag(True)
    #     self.__build_events(widgetItem, col, ["dbClick"])
    #     self.__run_events()
    def __init_events(self, eventType:str=None):
        """初始化不同事件类型的事件链,如果没有传入事件类型则清空所有事件链
        数据结构如下
            eventList = [
                {
                    "type":"itemPressed", # 事件类型
                    "actions":[ # 当前类型的事件列表执行顺序按list顺序执行
                        {
                            "action":lambda *args, **kwargs: print("itemPressed"), # 响应函数
                            "params":{} # 提供的参数列表
                        },
                    ]
                },
            ]
        """
        if not eventType:
            self.__events = []
        else:
            self.__events = self.__events or []  # 事件链集合
            self.__events.append({
                "type":eventType, # 事件类型
                "actions":[] # 当前类型的事件列表执行顺序按list顺序执行
            })
    def __get_last_event_actions(self):
        """获取最新事件链函数集合,用于补充事件函数"""
        try:
            return self.__events[-1].get("actions")
        except:
            print_error()
        return  []
    def __build_events(self, widgetItem, col, signalTypes, params=None, triggerWidgetItem=None):
        """构建事件链表
            widgetItem: 当前节点
            col: 当前列
            signalTypes: 信号类型列表
            params: 传递的参数
            triggerWidgetItem: 触发事件的节点,默认为False,如果为False则默认
        注意:
            根据事件链规则构建事件链
            signalTypes 中允许存在一个或者多个信号, 假设多个信号中其中一个信号不允许向上传递,则递归时会将对应的信号移除保证其他信号不受影响
        """
        # 初始化当前触发的节点
        triggerWidgetItem = triggerWidgetItem or widgetItem
        # 获取当前节点的gid
        gid = widgetItem._gid()
        # 要传递的信号
        passSignals= []
        for signalType in signalTypes:
            """遍历要处理的信号"""
            propagationStatus = self.__deal_propagation_role({
                "triggerWidgetItem":triggerWidgetItem,
                "signalType":signalType,
                "gid":gid,
                "widgetItem":widgetItem,
                "triggerItemIsBranch":triggerWidgetItem._is_branch(),
                "col":col,
                "otree":self,
            })
            if propagationStatus:
                """允许继续冒泡"""
                passSignals.append(signalType)

        if not is_empty(passSignals):
            """如果存在向上冒泡的信号则继续传递"""
            return self.__build_events(widgetItem.parent(), col, passSignals, triggerWidgetItem=triggerWidgetItem)
    def __deal_propagation_role(self, params:dict={}):
        """
            处理传递规则,注意这里的规则仅限当前节点(不一定是初始触发事件的节点),其规则不会向上传递

        """
        # 获取gid
        gid = params.get("gid")
        # 获取当前列
        col = params.get("col")
        # 获取信号类型
        signalType = params.get("signalType")
        # 获取当前事件类型要执行的函数
        lastEventActions = self.__get_last_event_actions()

        # 获取当前节点的事件
        events = self._gid_events(gid) or {}
        # 获取当前节点的绑定数据
        data = self._gid_data(gid)
        # 将当前列转换为字段名,用于获取cell事件信息
        field = self._col_to_field(col)
        # 获取当前节点的传播规则
        propagations = self._gid_propagations(gid) or {}
        # 获取当前节点的域
        scope =  self._gid_scope(gid) 

        triggerWidgetItem = params.get("triggerWidgetItem")
        triggerGid = triggerWidgetItem._gid()
        triggerData = self._gid_data(triggerGid) or {}
        widgetItem = params.get("widgetItem")
        # 获取当前信号的传播规则
        curPropagation = propagations.get(signalType, "default")
        # 获取当前cell响应函数
        curCellEventAction =  events.get(field, {}).get(signalType) 
        # 获取当前node响应函数
        curNodeEventAction = events.get(signalType)
        # 初始化信号对应执行函数的参数
        cbfParams = {
            **params,
            "data":data,
            "field":field,
            "triggerGid":triggerGid,
            "triggerData":triggerData,
            "otree":self,
            # TODO: 待定参数中增加两个函数 用于主动停止事件传播,stop 停止当前事件,stopAll 停止后续所有事件 如果增加则需要考虑特例 右键菜单事件如何处理
            # "stop":lambda : self.__init_events(),
            # "stopAll":lambda : self.__stop_all(), # 停止所有事件 


        }
        # 传播状态
        propagationStatus = False

        title = widgetItem.text(col)
        # print(f"当前cell: {title} 信号: {signalType} 传播规则: {curPropagation}, curCellEventAction: {curCellEventAction}, curNodeEventAction: {curNodeEventAction}")
        # 根据传播规则处理不同的事件
        if curPropagation == "no" :
            """ "no":不传递,仅执行cell绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,且不会再向上传递
                2. 如果是传播途径的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,且不会再向上传递
            """
            curCellEventAction and lastEventActions.append({
                "action":curCellEventAction,
                "params":{
                    **cbfParams,
                    "triggerType":"cell",
                }
            })
            propagationStatus = False
        elif curPropagation == "default" :
            """ 缺省值,传递. 允许向上传递
                1.  当前触发事件的节点或者传播途径的节点,cell和node绑定的事件都会执行(先执行cell在执行node),且继续向上传递
            """
            curCellEventAction and lastEventActions.append({
                "action":curCellEventAction,
                "params":{
                    **cbfParams,
                    "triggerType":"cell",
                }
            })
            curNodeEventAction and lastEventActions.append({
                "action":curNodeEventAction,
                "params":{
                    **cbfParams,
                    "triggerType":"node",
                }
            })
            propagationStatus = True
        elif curPropagation == "cell" :
            """ "cell": 传递,仅执行cell绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么只会执行cell绑定的对应事件,不会执行node绑定的对应事件,会继续向上传递
            """
            curCellEventAction and lastEventActions.append({
                "action":curCellEventAction,
                "params":{
                    **cbfParams,
                    "triggerType":"cell",
                }
            })
            propagationStatus = True
        elif curPropagation == "node" :
            """ "node": 传递,仅执行node绑定的对应事件
                1. 如果是当前触发事件的节点,那么只会执行node绑定的对应事件,cell绑定的对应事件会被忽略,会继续向上传递
                2. 如果是传播途径的节点,那么只会执行node绑定的对应事件,cell绑定的对应事件会被忽略,会继续向上传递
            """
            curNodeEventAction and lastEventActions.append({
                "action":curNodeEventAction,
                "params":{
                    **cbfParams,
                    "triggerType":"node",
                }
            })
            propagationStatus = True
        elif curPropagation == "current" :
            """ "current": 传递，仅当前节点执行
                1. 如果是当前触发事件的节点,那么会执行cell和node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么会忽略掉当前节点和cell绑定的事件,不执行但会继续向上传递
            """

            if widgetItem == triggerWidgetItem:
                """当前为触发事件的节点"""
                curCellEventAction and lastEventActions.append({
                    "action":curCellEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"cell",
                    }
                })
                curNodeEventAction and lastEventActions.append({
                    "action":curNodeEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"node",
                    }
                })
            propagationStatus = True
        elif curPropagation == "non-current" :
            """ "non-current": 传递，仅非当前节点执行
                1. 如果是当前触发事件的节点,那么不会执行cell和node绑定的对应事件,会继续向上传递
                2. 如果是传播途径的节点,那么会执行节点和cell绑定的事件,不执行但会继续向上传递
            """
            if widgetItem != triggerWidgetItem:
                """当前为触发事件的节点"""
                curCellEventAction and lastEventActions.append({
                    "action":curCellEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"cell",
                    }
                })
                curNodeEventAction and lastEventActions.append({
                    "action":curNodeEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"node",
                    }
                })
            propagationStatus = True
        if scope or not self._is_propagation():
            """
                如果有域则截断全部事件传播,其优先级高于传播规则
                如果整体设置未开启传播，则同样不会向上传播事件。
            """
            return False
        # 还需要注意的是如果当前节点是根节点则不也不能再向上传递了
        if not widgetItem.parent():
            """通过有没有值判断是否为根节点"""
            if propagationStatus:
                """如果是根节点且允许传递,那么会执行列头事件和tree事件"""
                curColEventAction = (self._col_to_event(col) or {}).get(signalType)
                curTreeEventAction= self._get_key_action(signalType)
                # 注意tree和列头不在设置传播规则因为没有必要只要设置了对应事件则全部都执行
                curColEventAction and lastEventActions.append({
                    "action":curColEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"header",
                    }
                })
                curTreeEventAction and lastEventActions.append({
                    "action":curTreeEventAction,
                    "params":{
                        **cbfParams,
                        "triggerType":"tree",
                    }
                })
            return False
        return propagationStatus
    def __run_events(self):
        """开始执行事件链
            1. 遍历事件链
            2. 执行事件链,每执行完一个就弹出一个,如果当前事件链空了则全部移除开始执行下一个事件链
            TODO:存在致命的问题: 如果在执行过程中的函数是中断执行的,比如弹出右键菜单，他会等着菜单关闭后在继续执行下去，但是正常我们需要的是只弹出一个右键菜单。
                因此右键菜单不能成为事件，而应该是各级返回菜单配置项，最终当前事件类型结束后在执行菜单。TODO:
        """
        try:
            while not is_empty(self.__events or []):
                event = self.__events[0]
                actions = event.get("actions", [])
                # 注意因为执行过程中可能主动执行了 事件停止函数 因此需要判断 self.__events 是否为空
                while not is_empty(actions) and not is_empty(self.__events):
                    action = actions[0]
                    try:
                        action.get("action")(action.get("params"))
                    except:
                        print_error()   
                    actions.pop(0)
                popItem = self.__events.pop(0)
                curEvent = popItem.get("type")  
                # 主动出发信号
                self.sigPopEvent.emit({
                    "event":curEvent, # 传递当前结束执行的事件类型
                })
                self._clear_temp_params(curEvent)
        except:
            print_error()

    # 销毁  =====================================
    def _clear_caches(self):
        self.__events = [] # 事件链集合
        super()._clear_caches()
    def _destroy(self):
        """销毁树
            1. 清理已关联的信号
            2. 清空可用信号列表
            3. 清空绑定事件
            4. 执行上级的销毁函数
        """
        # 清理信号
        self._disconnect_singals()
        # 清空可用信号列表
        self._clear_enable_signals()
        # 清空绑定事件
        self._clear_actions()
        # 执行上级的销毁函数用于销毁缓存等信息
        super()._destroy()

if __name__ == '__main__':
    """测试当前模块"""
    import sys
    import qdarkstyle
    from qdarkstyle.light.palette import LightPalette
    from qdarkstyle.dark.palette import DarkPalette
    def get_headers():
        return [
            {
                "title": "名称",
                "field": "name",
                "type": "lable" # 缺省默认
            },
            {
                "title": "类型",
                "field": "type",
                "type": "lable" # 缺省默认
            },
            {
                "title": "是否激活",
                "field": "enable",
                "type": "checkbox"
            },
        ]
    def get_test_data():
        """根据header创建测试数据"""
        return [

            {
                "name": "test1", "type": "type1", "enable": True,
                "data":[1,2,3],
                "propagations":{
                    "lbtnClick": "no",
                    "rbtnClick": "default",
                    "rmenu":"current",
                    "type":{
                        "lbtnClick": "cell",
                        "rbtnClick": "node",
                    }
                },
                "styles":{
                    "name":{
                        "bgcolor":"#00f",
                        "color":"#f00",
                    }
                    
                },
                "events":{
                    "lbtnClick": lambda params: print(f"test1 : lbtnClick ", ),
                    "rbtnClick": lambda params: print(f"test1 : rbtnClick ", ),
                    "rmenu":lambda params: print(f"test1 : rbtnClick ", ),
                    "type":{
                        "lbtnClick": lambda params: print(f"test1 : type1 lbtnClick ", ),
                        "rbtnClick": lambda params: print(f"test1 : type1 rbtnClick ", ),
                    }
                },
                "children":[
                    {"name": "test3", "type": "type3", "enable": True,
                        "events":{
                            "lbtnClick": lambda params: print(f"test3 : lbtnClick ", params),
                            "rmenu":   lambda params: print(f"test3 : rmenu ", params.get("otree")._set_temp_rmenus([
                                {
                                    "title":"菜单1",
                                    "action":lambda:print("菜单1")
                                },
                                { 
                                    "title":"菜单2",
                                    "children":[
                                        {
                                            "title":"子菜单1",
                                            "action":lambda:print("子菜单1")
                                        }
                                    ]
                                },
                                {}, # 分割线
                                { 
                                    "title":"菜单3",
                                    "children":[
                                        {
                                            "title":"子菜单2",
                                            "action":lambda:print("子菜单2")
                                        }
                                    ]
                                },
                            ])),
                        },
                        "children":[
                            {"name": "test5", "type": "type5", "enable": True,
                                "children":[
                                    {
                                        "name": "test7", "type": "type7", "enable": True,
                                        "propagations":{
                                            "lbtnClick": "no", # 一旦设置成no 则不会再向上查找事件
                                        },
                                        "events":{
                                            "type":{
                                                "lbtnClick": lambda params: print(f"test7 : type7 lbtnClick  propagations:no"),
                                            }
                                        },
                                    },
                                    {"name": "test8", "type": "type8", "enable": True,
                                        "propagations":{
                                            "lbtnClick": "cell",
                                        },
                                        "events":{
                                            "lbtnClick": lambda params: print(f"test8 : lbtnClick", ),
                                            "type":{
                                                "lbtnClick": lambda params: print(f"test8 : type8 lbtnClick", ),
                                            }
                                        },
                                    },
                                ]
                            },
                            {"name": "test6", "type": "type6", "enable": True},
                        ]
                    },
                    {"name": "test4", "type": "type4", "enable": False},
                ]
            },
            {"name": "test2", "type": "type2", "enable": False},
        ]
    app = QtWidgets.QApplication(sys.argv)
    tree = OpTree()
    # 注意如果不启用事件 那么跟调用DataTree类就没啥区别了
    #  ["itemClicked", "itemDoubleClicked", "itemActivated", "itemPressed", "currentItemChanged", "itemSelectionChanged", "customContextMenuRequested"]  #  "itemSelectionChanged", "currentItemChanged" 等事件
    tree._start_enable_signals(["itemClicked", "customContextMenuRequested"],useAll=False)
    tree.resize(800, 600)
    tree._set_headers(get_headers())
    tree._set_data(get_test_data())
    tree.setStyleSheet(qdarkstyle.load_stylesheet(qt_api="pyqt5", palette=DarkPalette))
    # 初始化tree的行为要放在初始化数据之后,因为每次初始化数据时都会先clear下数据以及定义好的事件
    tree.show()
    sys.exit(app.exec_())

